.. _глава_78_start:

Дополнительная информация о шаблонах
=====================================

.. index::
    template
    шаблоны

Мы познакомились с возможностями и удобством шаблонов в главе  (:ref:`Шаблоны<глава_64_start>`). Одного шаблонизированного определения алгоритма или структуры данных
достаточно, чтобы использовать это определение для нескольких типов.
В этой главе рассматривались только наиболее распространенные варианты использования шаблонов: шаблоны функций, структур, классов и т. д,
struct, шаблоны классов и их использование с параметрами шаблона типа. В
этой главе мы рассмотрим шаблоны более подробно. Прежде чем продолжить, я
рекомендую вам ознакомиться хотя бы с кратким содержанием этой главы

Синтаксис быстрого доступа
---------------------------
Помимо того, что D-шаблоны являются мощным инструментом, их легко определять и использовать, и они
очень хорошо читаются. Определить шаблон функции, структуры или класса так же просто, как
предоставить список параметров шаблона:


.. code-block:: d

    T twice(T)(T value)
    {
        return 2 * value;
    }

    class Fraction(T)
    {
        T numerator;
        T denominator;
        // ...
    }

Определения шаблонов, подобные приведенным выше, используют преимущества короткого синтаксиса D
синтаксиса шаблонов.
В своем полном синтаксисе шаблоны определяются ключевым словом template. На
Эквивалентами двух приведенных выше шаблонных определений будут следующие:

.. code-block:: d

    template twice(T)
    {
        T twice(T value)
        {
            return 2 * value;
        }
    }

    template Fraction(T)
    {
        class Fraction
        {
            T numerator;
            T denominator;
            // ...
        }
    }

Хотя большинство шаблонов определяются с помощью сокращенного синтаксиса, компилятор всегда
использует полный синтаксис. Мы можем представить, что компилятор применяет следующие шаги для
преобразования синтаксиса сокращения в его полную форму за кулисами:
#. Обернуть определение блоком шаблонов.
#. Дать такое же имя этому блоку.
#, Переместите список параметров шаблона в блок шаблона.
Полный синтаксис, полученный после этих шагов, называется одноименным шаблоном,
который программист может определить и в явном виде. Мы рассмотрим одноименные
шаблоны ниже.

**Пространство имен шаблонов**

Внутри блока шаблона может быть более одного определения.
Cледующий шаблон содержит определение функции и структуры:

.. code-block:: d

    template MyTemplate(T)
    {
        T foo(T value)
        {
            return value / 3;
        }

        struct S
        {
            T member;
        }
    }

Инстанцирование шаблона для определенного типа приводит к инстанцированию всех определений
внутри блока.
Следующий код инстанцирует шаблон для int и double:

.. code-block:: d

    auto result = MyTemplate!int.foo(42);
    writeln(result);
    auto s = MyTemplate!double.S(5.6);
    writeln(s.member);

Конкретная инстанция шаблона вводит пространство имен. Определения, которые
которые находятся внутри инстанса, могут использоваться под этим именем. Однако если эти имена
слишком длинные, всегда можно использовать псевдонимы, как мы уже видели в главе о псевдонимах (:ref:`alias и with<глава_66_start>`)

.. code-block:: d

    alias MyStruct = MyTemplate!dchar.S;
    // ...
    auto o = MyStruct('a');
    writeln(o.member);


**Одноименные шаблоны**

Одноименные шаблоны - это блоки шаблонов, содержащие определение, которое имеет
то же имя, что и у этого блока. Фактически, каждый синтаксис шаблона-ярлыка является ярлыком
одноименного шаблона.
В качестве примера предположим, что программе необходимо квалифицировать типы, размер которых превышает
более 20 байт, как слишком большие. Такая квалификация может быть достигнута с помощью константного значения bool
внутри блока шаблонов:

.. code-block:: d

    template isTooLarge(T)
    {
        enum isTooLarge = T.sizeof > 20;
    }

Обратите внимание, что имена блока шаблона и его единственного определения одинаковы
одинаковые. Этот одноименный шаблон используется в синтаксисе сокращения вместо
полного имени isTooLarge!int.isTooLarge

.. code-block:: d

    writeln(isTooLarge!int);

Выделенная (TODO) часть совпадает со значением bool внутри блока. Поскольку
размер int меньше 20, результатом кода будет false.
Этот одноименный шаблон можно определить и с помощью синтаксиса сокращения

.. code-block:: d

    enum isTooLarge(T) = T.sizeof > 20;

Чаще всего одноименные шаблоны используются для определения псевдонимов типов в зависимости от
определенных условий. Например, следующий одноименный шаблон выбирает
больший из двух типов, устанавливая для него псевдоним:

.. code-block:: d

    template LargerOf(A, B)
    {
        static if (A.sizeof < B.sizeof)
        {
            alias LargerOf = B;
        }
        else
        {
            alias LargerOf = A;
        }
    }

Поскольку long больше, чем int (8 байт против 4 байт), LargerOf!(int, long)
будет соответствовать типу long. Такие шаблоны особенно полезны в других
шаблонах, где два типа сами являются параметрами шаблона (или зависят от
параметров шаблона):

.. code-block:: d

    // ...
    /* The return type of this function is the larger of its two
    * template parameters: Either type A or type B. */
    auto calculate(A, B)(A a, B b)
    {
        LargerOf!(A, B) result;
        // ...
        return result;
    }

    void main()
    {
        auto f = calculate(1, 2L);
        static assert(is(typeof(f) == long));
    }

Виды шаблонов
--------------

**Шаблоны функций, классов и структур**

Мы уже рассказывали о шаблонах функций, классов и структур в главе Шаблоны (:ref:`Шаблоны<глава_64_start>`), и с тех пор мы видели множество их примеров.

**Шаблоны функций-членов**
Функции-члены struct и class также могут быть шаблонами. Например.
следующий шаблон функции-члена put() будет работать с любым типом параметров
если только этот тип совместим с операциями внутри шаблона (для данного
конкретного шаблона он должен быть преобразован в строку)

.. code-block:: d

    class Sink
    {
        string content;
        void put(T)(auto ref const T value)
        {
            import std.conv;

            content ~= value.to!string;
        }
    }

Однако, поскольку шаблоны могут иметь потенциально бесконечное число инстанций,
они не могут быть виртуальными функциями (:ref:`Насдедование<глава_55_start>`), поскольку компилятор не может знать.
какие конкретные инстансы шаблона следует включить в интерфейс.
(Соответственно, ключевое слово abstract также не может быть использовано).
Например, хотя наличие шаблона put() в следующем
подклассе может создать впечатление, что он переопределяет функцию, на самом деле он скрывает
имя put суперкласса (см. скрытие имен в главе о псевдонимах (:ref"`alias и with<глава_66_start>`))


.. code-block:: d

    class Sink
    {
        string content;
        void put(T)(auto ref const T value)
        {
            import std.conv;

            content ~= value.to!string;
        }
    }

    class SpecialSink : Sink
    {
        /* The following template definition does not override
    * the template instances of the superclass; it hides
    * those names. */
        void put(T)(auto ref const T value)
        {
            import std.string;

            super.put(format("{%s}", value));
        }
    }

    void fillSink(Sink sink)
    {
        /* The following function calls are not virtual. Because
    * parameter 'sink' is of type 'Sink', the calls will
    * always be dispatched to Sink's 'put' template
    * instances. */
        sink.put(42);
        sink.put("hello");
    }

    void main()
    {
        auto sink = new SpecialSink();
        fillSink(sink);
        import std.stdio;

        writeln(sink.content);
    }

В результате, хотя объект на самом деле является SpecialSink, оба вызова внутри
fillSink() отправляются на Sink, а содержимое не содержит фигурных
скобки, которые вставляет SpecialSink.put():

.. code-block:: console

    42hello ← Sink's behavior, not SpecialSink's

**Шаблоны объединений**

Шаблоны объединения похожи на шаблоны структуры. Синтаксис быстрого доступа доступен
и для них.
В качестве примера давайте разработаем более общую версию объединения IpAdress, которое
мы рассматривали в главе (:ref:`Объединения<глава_75_start>`). Там значение IPv4-адреса
хранилось как член uint в той ранней версии IpAdress, а тип элемента
сегментного массива был ubyte

.. code-block:: d

    union IpAddress
    {
        uint value;
        ubyte[4] bytes;
    }

Массив байтов обеспечивает легкий доступ к четырем сегментам адреса IPv4.
Эту же концепцию можно реализовать в более общем виде следующим образом
шаблон объединения:

.. code-block:: d

    union SegmentedValue(ActualT, SegmentT)
    {
        ActualT value;
        SegmentT[ /* number of segments */ ] segments;
    }


Этот шаблон позволил бы свободно указывать типы значения и его сегментов.
Количество необходимых сегментов зависит от типов фактического
значения и сегментов. Поскольку IPv4-адрес состоит из четырех байтовых сегментов, это
значение было жестко закодировано как 4 в более раннем определении IPAddress. Для
шаблона SegmentedValue количество сегментов должно быть вычислено во
время компиляции, когда создается экземпляр шаблона для двух конкретных типов.
Следующий одноименный шаблон использует свойства .sizeof
двух типов для вычисления необходимого количества сегментов:

.. code-block:: d

    template segmentCount(ActualT, SegmentT)
    {
        enum segmentCount = ((ActualT.sizeof + (SegmentT.sizeof - 1))
                    / SegmentT.sizeof);
    }

Короткий синтаксис может быть более удобным для чтения:

.. code-block:: d

    enum segmentCount(ActualT, SegmentT) =
        ((ActualT.sizeof + (SegmentT.sizeof - 1))
                / SegmentT.sizeof);

Примечание:ВыражениеSegmentT.sizeof - 1 предназначено для случаев, когда размеры типов не могут быть
разделить поровну. Например, если фактический тип равен 5 байтам, а тип сегмента - 2
байт, даже если всего требуется 3 сегмента, результат целочисленного деления 5/2
будет неверно равен 2.
Теперь определение шаблона объединения завершено

.. code-block:: d

    union SegmentedValue(ActualT, SegmentT)
    {
        ActualT value;
        SegmentT[segmentCount!(ActualT, SegmentT)] segments;
    }

Инстанцирование шаблона для uint и ubyte будет эквивалентно
предыдущему определению IpAddress:

.. code-block:: d

    import std.stdio;
    void main()
    {
        auto address = SegmentedValue!(uint, ubyte)(0xc0a80102);
        foreach (octet; address.segments)
        {
            write(octet, ' ');
        }
    }

Вывод программы такой же, как и в главе :ref:`Объединения<глава_75_start>`:

.. code-block:: console

    2 1 168 192

Чтобы продемонстрировать гибкость этого шаблона, представим, что требуется
получить доступ к частям IPv4-адреса в виде двух ushort-значений. Это было бы так же просто, как
указать ushort в качестве типа сегмента:

.. code-block:: d

    auto address = SegmentedValue!(uint, ushort)(0xc0a80102);

Хотя это и необычно для адреса IPv4, выход программы будет состоять из
из двух значений сегмента ushort:

.. code-block:: console

    258 49320

**Шаблоны интерфейсов**

Шаблоны интерфейсов обеспечивают гибкость в отношении типов, используемых в интерфейсе
(а также такие значения, как размеры массивов фиксированной длины и другие особенности интерфейса).
интерфейса).
Давайте определим интерфейс для цветных объектов, где тип цвета
определяется параметром шаблона:

.. code-block:: d

    interface ColoredObject(ColorT)
    {
        void paint(ColorT color);
    }

Этот шаблон интерфейса требует, чтобы его подтипы определяли функцию paint()
но оставляет тип цвета гибким.
Класс, представляющий рамку на веб-странице, может выбрать тип цвета
который представлен красным, зеленым и синим компонентами:

.. code-block:: d

    struct RGB
    {
        ubyte red;
        ubyte green;
        ubyte blue;
    }

    class PageFrame : ColoredObject!RGB
    {
        void paint(RGB color)
        {
        // ...
        }
    }

С другой стороны, класс, использующий частоту света, может выбрать
совершенно другой тип для представления цвета:

.. code-block:: d

    alias Frequency = double;
    class Bulb : ColoredObject!Frequency
    {
        void paint(Frequency color)
        {
        // ...
        }
    }

Однако, как объясняется в главе (:ref:`Шаблоны<глава_64_start>`), "каждая инстанция шаблона
инстанцирование дает отдельный тип". Соответственно, интерфейсы
ColoredObject!RGB и ColoredObject!Frequency являются несвязанными интерфейсами,
а PageFrame и Bulb - несвязанные классы.

Виды параметров шаблонов
------------------------
Все параметры шаблонов, которые мы рассматривали до сих пор, были параметрами типов.
До сих пор такие параметры, как T и ColorT, представляли типы. Например, T означало
int, double, Student и т. д. в зависимости от инстанцирования шаблона.
Существуют и другие виды шаблонных параметров: value, this, alias и tuple.

**Параметры шаблона типа**
Этот раздел приведен лишь для полноты картины. Все шаблоны, которые мы рассматривали до сих пор
имели параметры типа.

**Параметры шаблона значения**

Параметры шаблона значения позволяют гибко настраивать определенные значения, используемые в шаблоне
реализации.
Поскольку шаблоны являются функцией времени компиляции, значения параметров шаблона значения
должны быть известны во время компиляции; значения, которые должны быть вычислены во время выполнения
не могут быть использованы.
Чтобы увидеть преимущество параметров шаблона значения, давайте начнем с набора
структур, представляющих геометрические фигуры:

.. code-block:: d

        struct Triangle
        {
            Point[3] corners;
            // ...
        }

        struct Rectangle
        {
            Point[4] corners;
            // ...
        }

        struct Pentagon
        {
            Point[5] corners;
            // ...
        }

Предположим, что другие переменные-члены и функции-члены этих типов
абсолютно одинаковы, и единственным отличием является значение, определяющее
количество углов.
В таких случаях помогают параметры шаблона значения. Следующий шаблон struct
достаточен для представления всех вышеперечисленных типов и даже больше:

.. code-block:: d

    struct Polygon(size_t N)
    {
        Point[N] corners;
        // ...
    }

Единственным параметром шаблона struct является значение N типа
size_t. Значение N может быть использовано как константа времени компиляции в любом месте шаблона
шаблона.
Этот шаблон достаточно гибкий, чтобы представлять фигуры с любыми сторонами:

.. code-block:: d

    auto centagon = Polygon!100();

Следующие псевдонимы соответствуют предыдущим определениям структур:

.. code-block:: d

    alias Triangle = Polygon!3;
    alias Rectangle = Polygon!4;
    alias Pentagon = Polygon!5;
    // ...
    auto triangle = Triangle();
    auto rectangle = Rectangle();
    auto pentagon = Pentagon();


Тип параметра value template выше был size_t. До тех пор, пока значение
может быть известно во время компиляции, параметр шаблона значения может быть любого типа: фундаментального типа, типа struct, типа
фундаментальный тип, тип struct, массив, строка и т. д.

.. code-block:: d

    struct S
    {
        int i;
    }
    // Параметр шаблона значения struct S
    void foo(S s)()
    {
    // ...
    }
    void main()
    {
        foo!(S(42))(); // Инстанцирование с литералом S(42)
    }

В следующем примере используется строковый параметр шаблона для представления тега XML
для создания простого XML-вывода:
* Сначала тег между символами < >: <tag>
* Затем значение
* И наконец, тег между символами </ >: </tag>
*
Например, XML-тег, представляющий местоположение 42, будет напечатан следующим образом
<location>42</location>

Обратите внимание, что параметр шаблона не относится к типу, который используется в
реализации шаблона, а скорее о строковом значении. Это значение может
может быть использовано в любом месте шаблона как строка.
Элементы XML, которые нужны программе, можно определить как псевдонимы, как в следующем примере
следующий код:

.. code-block:: d

    alias Location = XmlElement!"location";
    alias Temperature = XmlElement!"temperature";
    alias Weight = XmlElement!"weight";
    void main()
    {
        Object[] elements;
        elements ~= new Location(1);
        elements ~= new Temperature(23);
        elements ~= new Weight(78);
        writeln(elements);
    }

Вывод:

.. code-block:: console

    [<location>1</location>, <temperature>23</temperature>, <weight>78</weight>]

Параметры шаблона значений могут иметь и значения по умолчанию. Например.
следующий шаблон struct представляет точки в многомерном пространстве, где
количество измерений по умолчанию равно 3:

.. code-block:: d

    struct Point(T, size_t dimension = 3)
    {
        T[dimension] coordinates;
    }

Этот шаблон можно использовать без указания параметра шаблона dimension:

.. code-block:: d

    Point!double center; // точка в 3-мерном пространстве.

При необходимости количество измерений все же может быть указано:

.. code-block:: d

    Point!(int, 2) point; // точка на поверхности

В главе :ref:`Переменное число параметров<глава_48_start>` мы видели, как
специальные ключевые слова работают по-разному в зависимости от того, появляются ли они внутри кода
или в качестве аргументов функции по умолчанию.
Аналогично, при использовании в качестве аргументов шаблона по умолчанию специальные ключевые слова относятся
к месту инстанцирования шаблона, а не к месту появления ключевых слов:

.. code-block:: d

    import std.stdio;

    void func(T,
        string functionName = __FUNCTION__,
        string file = __FILE__,
        size_t line = __LINE__)(T parameter)
    {
        writefln("Instantiated at function %s at file %s, line %s.",
            functionName, file, line);
    }

    void main()
    {
        func(42); // ← line 12
    }

Хотя специальные ключевые слова появляются в определении шаблона, их
их значения относятся к main(), в которой шаблон инстанцируется:

.. code-block:: console

    Instantiated at function deneme.main at file deneme.d, line 12

Ниже мы будем использовать __FUNCTION__ в примере перегрузки многомерных операторов.


**параметры шаблона для функций-членов**

Функции-члены также могут быть шаблонами. Их шаблонные параметры имеют те же
те же значения, что и у других шаблонов.
Однако, в отличие от других шаблонов, параметры шаблона функции-члена могут
также быть параметрами this. В этом случае идентификатор, стоящий после ключевого слова this
представляет собой точный тип ссылки this объекта. (Эта
ссылка означает сам объект, как это обычно записывается в конструкторах в виде
this.member = value).

.. code-block:: d

    struct MyStruct(T)
    {
        void foo(this OwnType)() const
        {
            writeln("Type of this object: ", OwnType.stringof);
        }
    }

Параметр шаблона OwnType - это фактический тип объекта, который вызывается функцией-членом
вызывается функция-член:

.. code-block:: d

    auto m = MyStruct!int();
    auto c = const(MyStruct!int)();
    auto i = immutable(MyStruct!int)();
    m.foo();
    c.foo();
    i.foo();

Вывод:

    Type of this object: MyStruct!int
    Type of this object: const(MyStruct!int)
    Type of this object: immutable(MyStruct!int)

Как видите, тип включает в себя соответствующий тип T, а также такие
такие квалификаторы, как const и immutable.
Структура (или класс) не обязательно должна быть шаблоном.
появляться и в шаблонах функций-членов нешаблонных типов.
Параметры этого шаблона могут быть полезны и в шаблонных миксинах, которые мы
рассмотрим двумя главами позже

**alias параметры шаблона**

Параметры alias шаблона могут соответствовать любому символу или выражению, которое
используемым в программе. Единственное ограничение на такой шаблонный аргумент - это то, что
аргумент должен быть совместим с его использованием внутри шаблона.
filter() и map() используют параметры шаблона-псевдонима для определения
операций, которые они выполняют.
Рассмотрим простой пример на шаблоне struct, который предназначен для модификации
существующей переменной. Шаблон struct принимает переменную в качестве alias параметра


.. code-block:: d

    struct MyStruct(alias variable)
    {
        void set(int value)
        {
            variable = value;
        }
    }

Функция-член просто присваивает свой параметр переменной, с помощью которой
создается экземпляр шаблона структуры. Эта переменная должна быть указана во
время создания экземпляра шаблона:

.. code-block:: d

    int x = 1;
    int y = 2;
    auto object = MyStruct!x();
    object.set(10);
    writeln("x: ", x, ", y: ", y);

В этой инстанциации параметр шаблона cоответствует переменной x

.. code-block:: console

    x: 10, y: 2

И наоборот, MyStruct!при создании экземпляра шаблона y переменная будет ассоциироваться
с y.

Давайте теперь создадим параметр alias, который представляет вызываемую сущность, аналогично
filter() и map():

.. code-block:: d

    void caller(alias func)()
    {
        write("calling: ");
        func();
    }

Как видно из скобок (), caller() использует свой параметр шаблона как
функцию. Кроме того, поскольку круглые скобки пустые, вызов функции
функции без указания аргументов.
Возьмем следующие две функции, которые соответствуют этому описанию. Они могут
обе представлять собой func, потому что в шаблоне они могут быть вызваны как func():

.. code-block:: d

    void foo()
    {
        writeln("foo called.");
    }

    void bar()
    {
        writeln("bar called.");
    }

Эти функции можно использовать в качестве параметра псевдонима в caller():

.. code-block:: d

    caller!foo();
    caller!bar();

Вывод:

.. code-block:: console

    calling: foo called.
    calling: bar called.


Любой символ может быть использован в качестве псевдопараметра, если он соответствует тому, как он используется в шаблоне.
в качестве параметра-псевдонима. В качестве контрпримера можно привести использование переменной int в функции caller()
приведет к ошибке компиляции:

.. code-block:: d

    int variable;
    caller!variable(); // ← ошибка компиляции

Ошибка компиляции указывает на то, что переменная не соответствует ее использованию в
шаблоне:

.. code-block:: console

    Error: function expected before (), not variable of type int

Хотя ошибка заключается в инстанцировании переменной caller!variable, ошибка компиляции обязательно указывает на func() внутри шаблона caller().
ошибка компиляции обязательно указывает на func() внутри шаблона caller()
потому что с точки зрения компилятора ошибка заключается в попытке вызвать
переменной в качестве функции. Одним из способов решения этой проблемы является использование ограничений шаблона
ограничений, которые мы рассмотрим ниже.
Если переменная поддерживает синтаксис вызова функции, возможно, потому что у нее есть
opCall() или является литералом функции, то она все равно будет работать с шаблоном caller()
шаблон. Следующий пример демонстрирует оба этих случая:

.. code-block:: d

    class C
    {
        void opCall()
        {
            writeln("C.opCall called.");
        }
    }
    // ...
    auto o = new C();
    caller!o();
    caller!(
    {
        writeln("Function literal called.");})();
    }

Вывод:

.. code-block:: d

    calling: C.opCall called.
    calling: Function literal called.

Параметры псевдонимов также могут быть специализированными. Однако для них используется другой
синтаксис специализации. Специализированный тип должен быть указан между ключевым словом alias
и именем параметра:

.. code-block:: d

    import std.stdio;

    void foo(alias variable)()
    {
        writefln("The general definition is using '%s' of type %s.",
            variable.stringof, typeof(variable).stringof);
    }

    void foo(alias int i)()
    {
        writefln("The int specialization is using '%s'.",
            i.stringof);
    }

    void foo(alias double d)()
    {
        writefln("The double specialization is using '%s'.",
            d.stringof);
    }

    void main()
    {
        string name;
        foo!name();
        int count;
        foo!count();
        double length;
        foo!length();
    }


Также обратите внимание, что параметры псевдонимов делают имена фактических переменных доступными
внутри шаблона:

.. code-block:: console

    The general definition is using 'name' of type string.
    The int specialization is using 'count'.
    The double specialization is using 'length'.

.. index::
    tuple
    кортеж

**Кортежи как параметры шаблона**

В главе :ref:`Переменное число параметров<глава_48_start>` мы видели, что
вариативные функции могут принимать любое количество и любой тип параметров. Для
например, writeln() может быть вызвана с любым количеством параметров любого типа.
Шаблоны также могут быть вариативными. Параметр шаблона, состоящий из
имени, за которым следует ..., допускает любое количество и тип параметров в этой
в позиции параметра. Внутри шаблона такие параметры отображаются в виде кортежа,
который можно использовать как AliasSeq.
Рассмотрим пример с шаблоном, который просто выводит информацию
о каждом аргументе шаблона, с которым он инстанцируется:

.. code-block:: d

    void info(T...)(T args)
    {
    // ...
    }

Параметр шаблона T... делает инфу вариативным шаблоном. И T, и args являются
кортежи:
* T представляет типы аргументов.
* args представляет сами аргументы.
В следующем примере шаблон функции инстанцируется с тремя значениями
трех различных типов:

.. code-block:: d

    import std.stdio;
    // ...
    void main()
    {
        info(1, "abc", 2.3);
    }

Следующая реализация просто печатает информацию об аргументах путем
перебирая их в цикле foreach:

.. code-block:: d

    void info(T...)(T args)
    {
        // 'args' is being used like a tuple:
        foreach (i, arg; args)
        {
            writefln("%s: %s argument %s",
                i, typeof(arg).stringof, arg);
        }
    }

Примечание:Как было показано в предыдущей главе, поскольку аргументы представляют собой кортеж, оператор foreach
представляет собой компилируемый оператор foreach.

Вывод:

.. code-block:: console

    0: int argument 1
    1: string argument abc
    2: double argument 2.3

Обратите внимание, что вместо того, чтобы получать тип каждого аргумента через typeof(arg), мы
могли бы использовать и T[i].
Мы знаем, что аргументы шаблонов могут быть выведены для шаблонов функций.
Поэтому в предыдущей программе компилятор выводит типы int, string и double.

Однако параметры шаблонов можно указывать и явно. Например,
например, std.conv.to принимает тип назначения в качестве явного шаблонного параметра
параметр:

.. code-block:: d

    to!string(42);

Если параметры шаблона указаны явно, они могут быть смешанными
значений, типов и других видов. Такая гибкость приводит к необходимости иметь возможность
определить, является ли каждый параметр шаблона типом или нет, чтобы тело

чтобы тело шаблона было закодировано соответствующим образом. Это достигается путем обработки аргументов
как AliasSeq.
Рассмотрим пример этого на примере шаблона функции, который выдает определения struct
определения в виде исходного кода в текстовом виде. Пусть эта функция возвращает
полученный исходный код в виде строки. Эта функция может сначала принять имя
struct, а затем типы и имена членов, заданные в виде пар:

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln(structDefinition!("Student",
                string, "name",
                int, "id",
                int[], "grades")());
    }

Ожидается, что при инстанцировании structDefinition будет получен следующий результат
строка:

.. code-block:: d

    struct Student
    {
        string name;
        int id;
        int[] grades;
    }

Примечание: Функции, создающие исходный код, используются с ключевым слово мixin, которое мы
мы рассмотрим в следующей главе :ref:`Примеси<глава_80_start>`.
Ниже приведена реализация, позволяющая получить желаемый результат. Обратите внимание
как в шаблоне функции используется выражение is. Помните, что
выражение is (arg) выдает true, когда arg является допустимым типом:

.. code-block:: d

    import std.string;
    string structDefinition(string name, Members...)()
    {
        /* Ensure that members are specified as pairs: first the
    * type then the name. */
        static assert((Members.length % 2) == 0,
            "Members must be specified as pairs.");
        /* The first part of the struct definition. */
        string result = "struct " ~ name ~ "\n{\n";
        foreach (i, arg; Members)
        {
            static if (i % 2)
            {
                /* The odd numbered arguments should be the names
    * of members. Instead of dealing with the names
    * here, we use them as Members[i+1] in the 'else'
    * clause below.
    *
    * Let's at least ensure that the member name is
    * specified as a string. */
                static assert(is(typeof(arg) == string),
                    "Member name " ~ arg.stringof ~
                        " is not a string.");
            }
            else
            {
                /* In this case 'arg' is the type of the
    * member. Ensure that it is indeed a type. */
                static assert(is(arg),
                    arg.stringof ~ " is not a type.");
                /* Produce the member definition from its type and
    * its name.
    *
    * Note: We could have written 'arg' below instead
    * of Members[i]. */
                result ~= format("    %s %s;\n",
                    Members[i].stringof, Members[i + 1]);
            }
        }
        /* The closing bracket of the struct definition. */
        result ~= "}";
        return result;
    }

    import std.stdio;

    void main()
    {
        writeln(structDefinition!("Student",
                string, "name",
                int, "id",
                int[], "grades")());
    }

.. index::
    typeof

typeof(this), typeof(super) и typeof(return).
---------------------------------------------

В некоторых случаях из-за общей природы шаблонов трудно определить или прописать
определенные типы в коде шаблона. Следующие три специальные разновидности typeof
полезны в таких случаях. Хотя они представлены в этой главе,
они работают и в нешаблонном коде.

* typeof(this) генерирует тип ссылки this. Она работает в любой
  структуре или классе, даже за пределами функций-членов:

.. code-block::

    struct List(T)
    {
        // The type of 'next' is List!int when T is int
        typeof(this)* next;
        // ...
    }

* typeof(super) генерирует базовый тип класса (т.е. тип super)

.. code-block:: d

    class ListImpl(T)
    {
        // ...
    }

    class List(T) : ListImpl!T
    {
        // The type of 'next' is ListImpl!int when T is int
        typeof(super)* next;
        // ...
    }

typeof(return) генерирует тип возвращаемой функции внутри самой функции.
Например, вместо того чтобы определить функцию calculate() выше как
auto, мы можем быть более явными, заменив auto на LargerOf!(A,
B) в ее определении. (Более явное определение будет иметь дополнительное преимущество в том, что
избавление от хотя бы части комментария к функции).

.. code-block:: d

    LargerOf!(A, B) calculate(A, B)(A a, B b)
    {
    // ...
    }

typeof(return) предотвращает необходимость повторять тип возврата внутри функции
тело:

.. code-block:: d

    LargerOf!(A, B) calculate(A, B)(A a, B b)
    {
        typeof(return) result; // The type is either A or B
        // ...
        return result;
    }

Специализации шаблонов
----------------------

Мы уже рассматривали специализации шаблонов в главе (:ref:`Шаблоны<глава_64_start>`). Как и
параметры типа, другие виды параметров шаблона также могут быть специализированы.
Ниже приведено общее определение шаблона и его специализация для 0:

.. code-block:: d

    void foo(int value)()
    {
        // ... general definition ...
    }

    void foo(int value : 0)()
    {
        // ... special definition for zero ...
    }

Мы воспользуемся преимуществами специализации шаблонов в разделе метапрограммирования
ниже.

Метапрограммирование
--------------------

Поскольку шаблоны связаны с генерацией кода, они относятся к функциям более высокого уровня.
D. Шаблон - это код, который генерирует код. Написание кода, который генерирует
код,  называется метапрограммированием.
Благодаря тому, что шаблоны являются функциями времени компиляции, некоторые операции, которые обычно выполняются
обычно выполняются во время выполнения, могут быть перенесены на время компиляции в виде
инстанцирования.
(Примечание: выполнение функций во время компиляции (CTFE) - это другая возможность, которая достигает той же цели.
той же цели. Мы рассмотрим CTFE в одной из следующих глав.)
Выполнение шаблонов во время компиляции обычно основано на рекурсивных
инстанцирования.
Чтобы увидеть пример этого, давайте сначала рассмотрим обычную функцию, которая вычисляет
сумму чисел от 0 до определенного значения. Например, когда ее аргументом является
4, эта функция должна вернуть результат 0+1+2+3+4

.. code-block:: d

    int sum(int last)
    {
        int result = 0;
        foreach (value; 0 .. last + 1)
        {
            result += value;
        }
        return result;
    }

Это итеративная реализация функции. Та же самая функция может быть
также реализовать с помощью рекурсии:

.. code-block:: d

    int sum(int last)
    {
        return (last == 0
                ? last : last + sum(last - 1));
    }

Рекурсивная функция возвращает сумму последнего значения и предыдущей суммы. Как
вы видите, функция завершает рекурсию, специально обрабатывая значение 0
особым образом.
Функции обычно являются средствами времени выполнения. Как обычно, функция sum() может быть выполнена во время
во время выполнения:

.. code-block:: d

    writeln(sum(4));

Когда результат необходим во время компиляции, одним из способов достижения того же
вычисления является определение шаблона функции. В этом случае параметр должен быть
параметром шаблона, а не параметром функции:

.. code-block:: d

    // WARNING: This code is incorrect.
    int sum(int last)()
    {
        return (last == 0
                ? last : last + sum!(last - 1)());
    }

Этот шаблон функции инстанцирует себя на last - 1 и пытается вычислить
сумму снова с помощью рекурсии. Однако этот код неверен.
Поскольку троичный оператор будет скомпилирован для выполнения во время выполнения, не существует
нет проверки условия, которая бы прервала рекурсию во время компиляции:

.. code-block:: d

    writeln(sum!4()); // ← ошибка компиляции

Компилятор обнаруживает, что экземпляры шаблонов будут рекурсировать бесконечно, и
останавливается на произвольном количестве рекурсий:

.. code-block:: console

    Error: template instance deneme.sum!(-296) recursive expansion

Учитывая разницу между аргументом шаблона 4 и -296, компилятор
компилятор по умолчанию ограничивает расширение шаблонов на уровне 300.
В метапрограммировании рекурсия завершается специализацией шаблона.
Следующая специализация для 0 дает ожидаемый результат:

.. code-block:: d

    // The general definition int sum(int last)()
    {
        return last + sum!(last - 1)();
    }
    // The special definition for zero
    int sum(int last : 0)()
    {
        return 0;
    }

Ниже приведена программа, тестирующая sum():

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln(sum!4());
    }

Теперь программа успешно компилируется и выдает результат 4+3+2+1+0

.. code-block:: d

    10

Важным моментом является то, что функция sum!4() выполняется полностью
во время компиляции. Скомпилированный код эквивалентен вызову функции writeln() с
литералом 10:

.. code-block:: d

    writeln(10);

В результате скомпилированный код получается максимально быстрым и простым. Хотя значение
10 по-прежнему вычисляется как результат 4+3+2+1+0, все вычисления происходят во время
время компиляции.
Предыдущий пример демонстрирует одно из преимуществ метапрограммирования:
перенос операций из времени выполнения в время компиляции. CTFE избавляет от некоторых
идиом метапрограммирования в D.

Полиморфизм времени компиляции
--------------------------------

В объектно-ориентированном программировании (ООП) полиморфизм достигается за счет наследования.
Например, если функция принимает интерфейс, она принимает объекты любого класса, который
наследует этот интерфейс.
Давайте вспомним пример из предыдущей главы:

.. code-block:: d

    import std.stdio;

    interface SoundEmitter
    {
        string emitSound();
    }

    class Violin : SoundEmitter
    {
        string emitSound()
        {
            return "♩♪♪";
        }
    }

    class Bell : SoundEmitter
    {
        string emitSound()
        {
            return "ding";
        }
    }

    void useSoundEmittingObject(SoundEmitter object)
    {
        // ... some operations ...
        writeln(object.emitSound());
        // ... more operations ...
    }

    void main()
    {
        useSoundEmittingObject(new Violin);
        useSoundEmittingObject(new Bell);
    }

UseSoundEmittingObject() выигрывает от полиморфизма. Она принимает
SoundEmitter, чтобы его можно было использовать с любым типом, производным от этого
интерфейса.
Поскольку работа с любым типом присуща шаблонам, их можно рассматривать как
они также обеспечивают своего рода полиморфизм. Являясь функцией времени компиляции, шаблоны
полиморфизм, который обеспечивают шаблоны, называется полиморфизмом компиляции.
И наоборот, полиморфизм ООП называется полиморфизмом времени выполнения.
В действительности ни один из видов полиморфизма не может быть использован с любым типом
потому что типы должны удовлетворять определенным требованиям.
Полиморфизм времени выполнения требует, чтобы тип реализовывал определенный интерфейс.
Полиморфизм времени компиляции требует, чтобы тип был совместим с тем, как он
как он используется в шаблоне. Пока код компилируется, аргумент шаблона может быть
использоваться с этим шаблоном. (Примечание: опционально аргумент должен удовлетворять ограничениям шаблона
ограничениям. Ограничения шаблона мы рассмотрим ниже).
Например, если бы функция useSoundEmittingObject() была реализована как функция
вместо функции, ее можно было бы использовать с любым типом, поддерживающим функцию
object.emitSound():

.. code-block:: d

    void useSoundEmittingObject(T)(T object)
    {
        // ... some operations ...
        writeln(object.emitSound());
        // ... more operations ...
    }

    class Car
    {
        string emitSound()
        {
            return "honk honk";
        }
    }
    // ...
    useSoundEmittingObject(new Violin);
    useSoundEmittingObject(new Bell);
    useSoundEmittingObject(new Car);

Обратите внимание, что хотя Car не имеет отношений наследования ни с одним другим типом, код
код успешно компилируется, и функция-член emitSound() каждого типа
будет вызвана.
Полиморфизм времени компиляции также известен как "утиная типизация" - шутливый термин,
подчеркивающий поведение, а не реальный тип.

Разбухание кода
----------------

Код, генерируемый компилятором, отличается для каждого аргумента
типа, параметра значения и т.д.
Причину этого можно увидеть, если рассматривать int и double как аргументы типа
аргументов шаблона. Каждый тип должен обрабатываться разными типами
регистров процессора. По этой причине один и тот же шаблон должен быть скомпилирован по-разному
для разных аргументов шаблона. Другими словами, компилятор должен генерировать
разный код для каждой инстанциации шаблона.
Например, если бы функция useSoundEmittingObject() была реализована в виде шаблона,
она была бы скомпилирована столько раз, сколько различных инстанций
его.
Поскольку это приводит к увеличению размера программы, такой эффект называется раздуванием кода.
Хотя в большинстве программ это не является проблемой, это эффект шаблонов, о котором
который необходимо знать.
И наоборот, нешаблонная версия useSoundEmittingObject() не будет
не будет повторения кода. Компилятор скомпилирует эту функцию только один раз и
выполнит один и тот же код для всех типов интерфейса SoundEmitter. Во время выполнения
полиморфизме, когда один и тот же код ведет себя по-разному для разных типов.
достигается с помощью указателей функций на фоне. Хотя указатели функций
имеют небольшую стоимость во время выполнения, эта стоимость не является существенной в большинстве программ
Поскольку и разрастание кода, и полиморфизм во время выполнения влияют на производительность программы
Поскольку и разрастание кода, и полиморфизм времени выполнения влияют на производительность программы, невозможно заранее определить, будет ли полиморфизм времени выполнения или полиморфизм времени компиляции лучшим подходом
или полиморфизм во время компиляции будет лучшим подходом для конкретной
программы.

Ограничения шаблонов
--------------------

Тот факт, что шаблоны могут быть инстанцированы с любым аргументом, но не каждый
аргумент совместим с каждым шаблоном, приносит неудобства. Если
аргумент шаблона несовместим с конкретным шаблоном, то
несовместимость обязательно обнаруживается во время компиляции кода шаблона
кода для этого аргумента. В результате ошибка компиляции указывает на строку внутри
реализации шаблона.
Рассмотрим это на примере использования функции useSoundEmittingObject() с типом, который не
поддерживает вызов object.emitSound()

.. code-block:: d

    class Cup
    {
    // ... does not have emitSound() ...
    }
    // ...
    useSoundEmittingObject(new Cup); // ← incompatible type

Хотя, возможно, ошибка связана с кодом, который использует шаблон с
несовместимого типа, ошибка компиляции указывает на строку внутри шаблона:

.. code-block:: d

    void useSoundEmittingObject(T)(T object)
    {
    // ... некоторые операции ...
        writeln(object.emitSound()); // ← ОШИБКА компиляции
    // ... больше операций ...
    }

Нежелательным следствием является то, что если шаблон является частью стороннего
библиотечного модуля, ошибка компиляции будет выглядеть как проблема с самой
самой библиотеки.
Обратите внимание, что для интерфейсов такой проблемы не существует: Функция, принимающая
интерфейс, может быть вызвана только с типом, реализующим этот интерфейс.
Попытка вызвать такую функцию с любым другим типом приводит к ошибке компиляции у
вызывающей стороны.
Шаблонные ограничения предназначены для предотвращения некорректных инстанций шаблонов.
Они определяются как логические выражения условия if непосредственно перед
телом шаблона:

.. code-block:: d

    void foo(T)()
        if (/* ... constraints ... */) {
        // ...
    }

Определение шаблона учитывается компилятором только в том случае, если его ограничения оцениваются
в true для конкретной инстанциации шаблона. В противном случае определение шаблона
определение игнорируется для данного использования.
Поскольку шаблоны являются функцией времени компиляции, ограничения шаблона должны быть
оцениваемыми во время компиляции. Выражение is, которое мы рассматривали в главе :ref:`Выражение is<глава_71_start>`
часто используется в шаблонных ограничениях. Мы будем использовать выражение is
и в следующих примерах.

**Параметр кортежа из одного элемента**

Иногда единственный параметр шаблона должен быть одного из типов, значений или
псевдонимов. Этого можно добиться с помощью кортежного параметра длины один:

.. code-block:: d

    template myTemplate(T...) if (T.length == 1)
    {
        static if (is(T[0]))
        {
            // The single parameter is a type
            enum bool myTemplate =  /* ... */ ;
        }
        else
        {
            // The single parameter is some other kind
            enum bool myTemplate =  /* ... */ ;
        }
    }

Некоторые шаблоны модуля std.traits используют эту идиому в своих интересах.
Мы рассмотрим std.traits в одной из следующих глав.

**Именованные ограничения**

Иногда ограничения бывают сложными, что затрудняет понимание
требования к параметрам шаблона. С этой сложностью можно справиться с помощью
идиома, которая эффективно дает имена ограничениям. Эта идиома сочетает в себе четыре
особенности D: анонимные функции, typeof, выражение is и одноименные
шаблоны.
Рассмотрим это на шаблоне функции, имеющей параметр типа. Шаблон
использует свой параметр функции особым образом:

.. code-block:: d

    void use(T)(T object)
    {
    // ...
        object.prepare();
    // ...
        object.fly(42);
    // ...
        object.land();
    // ...
    }

Как видно из реализации шаблона, типы, с которыми может работать эта функция
функция может работать с ними, должны поддерживать три специфических вызова функций на объекте:
prepare(), fly(42) и land().
Один из способов указать ограничение шаблона для этого типа - с помощью выражений is и
typeof для каждого вызова функции внутри шаблона:

.. code-block:: d

    void use(T)(T object)
        if (is(typeof(object.prepare())) &&
        is(typeof(object.fly(1))) &&
        is(typeof(object.land())))
    {
        // ...
    }

Я объясню этот синтаксис ниже. Пока же примите всю конструкцию как есть
is (typeof(object.prepare())) означает, поддерживает ли тип вызов
.prepare().
Хотя такие ограничения достигают желаемой цели, иногда они слишком
сложными, чтобы их можно было читать. Вместо этого можно дать более описательное имя
всему ограничению:

.. code-block:: d

    void use(T)(T object)
        if (canFlyAndLand!T) {
        // ...
    }

Это ограничение стало более читабельным, потому что теперь более ясно, что шаблон
предназначен для работы с типами, которые могут летать и приземляться.
Такие ограничения достигаются с помощью идиомы, которая реализуется аналогично
следующий одноименный шаблон:

.. code-block:: d


    template canFlyAndLand(T)
    {
        enum canFlyAndLand = is(typeof(
        {
                    T object;
                    object.prepare(); // should be preparable for flight
                    object.fly(1); // should be flyable for a certain distance
                    object.land(); // should be landable
                }()));
    }

Особенности D, которые участвуют в этой идиоме, и то, как они взаимодействуют друг с другом
объясняется ниже:

.. code-block:: d

    template canFlyAndLand(T)
    {
        //        (6)        (5)  (4)
        enum canFlyAndLand = is(typeof(
        { // (1)
                    T object; // (2)
                    object.prepare();
                    object.fly(1);
                    object.land();
                    // (3)
                }()));
    }

#. Анонимная функция: Мы уже знакомились с анонимными функциями в главе :ref:`Функции Указатели, делегаты и ламбды<глава_72_start>`.
Выделенные фигурные
скобки выше определяют анонимную функцию.
#. Функциональный блок: Блок функций использует тип так, как он должен использоваться
в реальном шаблоне. Сначала определяется объект этого типа, а затем этот
объект используется определенным образом. (Этот код никогда не выполняется, см. ниже).
#. Оценка функции: Пустые круглые скобки в конце
анонимной функции обычно выполняют эту функцию. Однако, поскольку этот вызов
находится внутри typeof, он никогда не выполняется.
#. Выражение typeof: typeof выдает тип выражения.
Важный факт о typeof заключается в том, что он никогда не выполняет выражение.
Скорее, он выдает тип выражения, если это выражение было бы
выполнено:

.. code-block:: d

    int i = 42;
    typeof(++i) j; // same as 'int j;'
    assert(i == 42); // ++i has not been executed

Как показывает предыдущее утверждение, выражение ++i не было выполнено.
typeof просто выдал тип этого выражения как int.
Если выражение, которое получает typeof, недопустимо, typeof не выдает никакого типа
вообще (даже void). Таким образом, если анонимная функция внутри canFlyAndLand
может быть успешно скомпилирована для T, typeof выдает допустимый тип. В противном случае
не выдает никакого типа.

#. Выражение is: Мы рассмотрели множество различных вариантов использования выражения is в
главе :ref:`Выражение is<глава_71_start>`. Синтаксис is (Type) выдает true, если
Тип действителен:

.. code-block:: d

    int i;
    writeln(is (typeof(i))); // true
    writeln(is (typeof(nonexistentSymbol))); // false

Несмотря на то, что второй typeof выше получает несуществующий символ, компилятор
компилятор не выдает ошибку компиляции. Скорее, эффект заключается в том, что выражение
typeof не дает никакого типа, поэтому выражение is выдает
false

.. code-block:: console

    true
    false

Одноименный шаблон: Как описано выше, поскольку шаблон canFlyAndLand
содержит одноименное определение, инстанцией шаблона является это
само определение.
В итоге use() приобретает более описательное ограничение

.. code-block:: d

    void use(T)(T object)     if (canFlyAndLand!T) {
    // ...
    }

Попробуем использовать этот шаблон с двумя типами, один из которых удовлетворяет ограничению, а другой
один, который не удовлетворяет ограничению:

.. code-block:: d

    // A type that does match the template's operations
    class ModelAirplane
    {
        void prepare()
        {
        }

        void fly(int distance)
        {
        }

        void land()
        {
        }
    }
    // A type that does not match the template's operations
    class Pigeon
    {
        void fly(int distance)
        {
        }
    }
    // ...
    use(new ModelAirplane); // ← компилируется
    use(new Pigeon); // ← ошибка компиляции

Именованный или нет, но поскольку шаблон имеет ограничение, ошибка компиляции указывает на
на строку, где используется шаблон, а не на строку, где он реализован.

Использование шаблонов в многомерной перегрузке операторов
----------------------------------------------------------

В главе :ref:`Перегрузка операторов<глава_53_start>` мы видели, что opDollar,
opIndex и opSlice предназначены для индексации и нарезки элементов. При перегрузке для
одномерных коллекций эти операторы выполняют следующие функции
обязанности:
* opDollar: Возвращает количество элементов коллекции.
* opSlice: Возвращает объект, представляющий некоторые или все элементы коллекции
коллекции.
* opIndex: Предоставляет доступ к элементу.

У этих функций-операторов также есть шаблонизированные версии, которые отличаются
от нешаблонных. Особенно обратите внимание на то, что при многомерной
многомерной перегрузке операторов opIndex берет на себя ответственность за
opSlice

* Шаблон opDollar: Возвращает длину определенного измерения
  коллекции. Размерность определяется параметром шаблона:

.. code-block:: d

    size_t opDollar(size_t dimension)() const {
    // ...
    }

* Шаблон opSlice: Возвращает информацию, определяющую диапазон элементов (например, начальное и конечное значения в массиве[begin..end]). Информация
  может быть возвращена в виде Tuple!(size_t, size_t) или эквивалентного
  тип. Размерность, которую задает диапазон, определяется шаблоном параметром:

.. code-block:: d

    Tuple!(size_t, size_t) opSlice(size_t dimension)(size_t begin,     size_t end)
    {
        return tuple(begin, end);
    }

* Шаблон opIndex: Возвращает объект диапазона, который представляет часть коллекции. Диапазон элементов определяется параметрами шаблона:

.. code-block:: d

    Range opIndex(A...)(A arguments) {
    // ...
    }

opIndexAssign и opIndexOpAssign также имеют шаблонные версии, которые
работают с диапазоном элементов коллекции. Пользовательские типы, определяющие эти операторы, можно использовать с многомерным
синтаксисом многомерного индексирования и нарезки

.. code-block:: d

    // Assigns 42 to the elements specified by the
    // indexing and slicing arguments:
    m[a, b..c, $-1, d..e] = 42;
    //              ↑   ↑     ↑    ↑
    // dimensions:  0   1     2    3


Такие выражения сначала преобразуются в те, которые вызывают функции-операторы.
Преобразования выполняются путем замены символов $ на вызовы функции opDollar!dimension(), а диапазоны индексов - вызовами
opSlice!dimension(begin, end). Информация о длине и диапазоне, которая
возвращаемая этими вызовами, в свою очередь используется в качестве аргументов при вызове, например, opIndexAssign.
opIndexAssign. Соответственно, приведенное выше выражение выполняется следующим образом
эквивалент (значения размерности выделены):

.. code-block:: d

    // The equivalent of the above:
    m.opIndexAssign(
    42, // ← value to assign
    a, // ← argument for dimension 0
    m.opSlice!1(b, c), // ← argument for dimension 1
    m.opDollar!2() - 1, // ← argument for dimension 2
    m.opSlice!3(d, e)); // ← argument for dimension 3

Следовательно, opIndexAssign определяет диапазон элементов из
аргументов.

**Пример перегрузки многомерных операторов**

Следующий пример Matrix демонстрирует, как эти операторы могут быть
перегружены для двумерного типа.
Обратите внимание, что этот код может быть реализован более эффективными способами. Например,
вместо построения одноэлементной подматрицы даже при работе с
например, по m[i, j], можно применить операцию непосредственно к этому элементу.
элементе.
Кроме того, выражения writeln(__FUNCTION__) внутри функций
не имеют никакого отношения к поведению кода. Они просто помогают раскрыть
функции, которые вызываются за сценой для различных вариантов использования операторов.
Также обратите внимание, что корректность значений размерности обеспечивается шаблонными
ограничениями.


.. code-block:: d

    import std.stdio;
    import std.format;
    import std.string;

    /* Works as a two-dimensional int array. */
    struct Matrix
    {
    private:
        int[][] rows;
        /* Represents a range of rows or columns. */
        struct Range
        {
            size_t begin;
            size_t end;
        }
        /* Returns the sub-matrix that is specified by the row and
    * column ranges. */
        Matrix subMatrix(Range rowRange, Range columnRange)
        {
            writeln(__FUNCTION__);
            int[][] slices;
            foreach (row; rows[rowRange.begin .. rowRange.end])
            {
                slices ~= row[columnRange.begin .. columnRange.end];
            }
            return Matrix(slices);
        }

    public:
        this(size_t height, size_t width)
        {
            writeln(__FUNCTION__);
            rows = new int[][](height, width);
        }

        this(int[][] rows)
        {
            writeln(__FUNCTION__);
            this.rows = rows;
        }

        void toString(void delegate(const(char)[]) sink) const
        {
            sink.formattedWrite!"%(%(%5s %)\n%)"(rows);
        }
        /* Assigns the specified value to each element of the
    * matrix. */
        Matrix opAssign(int value)
        {
            writeln(__FUNCTION__);
            foreach (row; rows)
            {
                row[] = value;
            }
            return this;
        }
        /* Uses each element and a value in a binary operation
    * and assigns the result back to that element. */
        Matrix opOpAssign(string op)(int value)
        {
            writeln(__FUNCTION__);
            foreach (row; rows)
            {
                mixin("row[] " ~ op ~ "= value;");
            }
            return this;
        }
        /* Returns the length of the specified dimension. */
        size_t opDollar(size_t dimension)() const
        if (dimension <= 1)
        {
            writeln(__FUNCTION__);
            static if (dimension == 0)
            {
                /* The length of dimension 0 is the length of the
    * 'rows' array. */
                return rows.length;
            }
            else
            {
                /* The length of dimension 1 is the lengths of the
    * elements of 'rows'. */
                return rows.length ? rows[0].length : 0;
            }
        }
        /* Returns an object that represents the range from
    * 'begin' to 'end'.
    *
    * Note: Although the 'dimension' template parameter is
    * not used here, that information can be useful for other
    * types. */
        Range opSlice(size_t dimension)(size_t begin, size_t end) if (dimension <= 1)
        {
            writeln(__FUNCTION__);
            return Range(begin, end);
        }
        /* Returns a sub-matrix that is defined by the
    * arguments. */
        Matrix opIndex(A...)(A arguments) if (A.length <= 2)
        {
            writeln(__FUNCTION__);
            /* We start with ranges that represent the entire
    * matrix so that the parameter-less use of opIndex
    * means "all of the elements". */
            Range[2] ranges = [Range(0, opDollar!0),
                Range(0, opDollar!1)];
            foreach (dimension, a; arguments)
            {
                static if (is(typeof(a) == Range))
                {
                    /* This dimension is already specified as a
    * range like 'matrix[begin..end]', which can
    * be used as is. */
                    ranges[dimension] = a;
                }
                else static if (is(typeof(a) : size_t))
                {
                    /* This dimension is specified as a single
    * index value like 'matrix[i]', which we want
    * to represent as a single-element range. */
                    ranges[dimension] = Range(a, a + 1);
                }
                else
                {
                    /* We don't expect other types. */
                    static assert(
                        false, format("Invalid index type: %s",
                            typeof(a).stringof));
                }
            }
            /* Return the sub-matrix that is specified by
    * 'arguments'. */
            return subMatrix(ranges[0], ranges[1]);
        }
        /* Assigns the specified value to each element of the
    * sub-matrix. */
        Matrix opIndexAssign(A...)(int value, A arguments) if (A.length <= 2)
        {
            writeln(__FUNCTION__);
            Matrix subMatrix = opIndex(arguments);
            return subMatrix = value;
        }
        /* Uses each element of the sub-matrix and a value in a
    * binary operation and assigns the result back to that
    * element. */
        Matrix opIndexOpAssign(string op, A...)(int value,
            A arguments) if (A.length <= 2)
        {
            writeln(__FUNCTION__);
            Matrix subMatrix = opIndex(arguments);
            mixin("return subMatrix " ~ op ~ "= value;");
        }
    }
    /* Executes the expression that is specified as a string, and
    * prints the result as well as the new state of the
    * matrix. */
    void execute(string expression)(Matrix m)
    {
        writefln("\n--- %s ---", expression);
        mixin("auto result = " ~ expression ~ ";");
        writefln("result:\n%s", result);
        writefln("m:\n%s", m);
    }

    void main()
    {
        enum height = 10;
        enum width = 8;
        auto m = Matrix(height, width);
        int counter = 0;
        foreach (row; 0 .. height)
        {
            foreach (column; 0 .. width)
            {
                writefln("Initializing %s of %s",
                    counter + 1, height * width);
                m[row, column] = counter;
                ++counter;
            }
        }
        writeln(m);
        execute!("m[1, 1] = 42")(m);
        execute!("m[0, 1 .. $] = 43")(m);
        execute!("m[0 .. $, 3] = 44")(m);
        execute!("m[$-4 .. $-1, $-4 .. $-1] = 7")(m);
        execute!("m[1, 1] *= 2")(m);
        execute!("m[0, 1 .. $] *= 4")(m);
        execute!("m[0 .. $, 0] *= 10")(m);
        execute!("m[$-4 .. $-2, $-4 .. $-2] -= 666")(m);
        execute!("m[1, 1]")(m);
        execute!("m[2, 0 .. $]")(m);
        execute!("m[0 .. $, 2]")(m);
        execute!("m[0 .. $ / 2, 0 .. $ / 2]")(m);
        execute!("++m[1..3, 1..3]")(m);
        execute!("--m[2..5, 2..5]")(m);
        execute!("m[]")(m);
        execute!("m[] = 20")(m);
        execute!("m[] /= 4")(m);
        execute!("(m[] += 5) /= 10")(m);
    }


Резюме
-------

В предыдущей главе, посвященной шаблонам, были сделаны следующие напоминания:

* Шаблоны определяют код как шаблон, чтобы компилятор мог генерировать его экземпляры
  его в соответствии с реальным использованием в программе.
* Шаблоны - это функция времени компиляции.
* Достаточно указать списки параметров шаблона, чтобы сделать шаблонами функции, структуры и
  определения классов шаблонами.
*  Аргументы шаблона могут быть указаны явно после восклицательного знака.
   Скобки не нужны, если внутри скобок находится только одна лексема скобки.
* Каждая инстанция шаблона дает свой тип.
* Аргументы шаблона могут быть выведены только для шаблонов функций.
* Шаблоны могут быть специализированы для типа, стоящего после символа :.
* Аргументы шаблона по умолчанию указываются после символа =.

В этой главе добавлены следующие понятия:

* Шаблоны могут быть определены с помощью полного или сокращенного синтаксиса.
* Область действия шаблона - это пространство имен.
* Шаблон, содержащий определение с тем же именем, что и шаблон, называется
  называется одноименным шаблоном. Шаблон представляет это определение.
* Шаблоны могут быть функциями, классами, структурами, объединениями и интерфейсами, и
  каждое тело шаблона может содержать любое количество определений.
* Параметры шаблона могут быть типами, значениями, this, псевдонимами и кортежами.
* В шаблонах полезны typeof(this), typeof(super) и typeof(return).
* Шаблоны могут быть специализированы для определенных аргументов.
* Метапрограммирование - это способ выполнения операций во время компиляции.
* Шаблоны обеспечивают полиморфизм во время компиляции.
* Раздельная генерация кода для различных инстанций может привести к раздуванию кода.
* Ограничения шаблонов ограничивают использование шаблонов для определенных
  аргументов. Они помогают перенести ошибки компиляции с реализаций
  шаблонов туда, где шаблоны действительно используются некорректно.
* Давать имена шаблонным ограничениям удобнее для чтения.
* Шаблонные версии opDollar, opSlice, opIndex, opIndexAssign и
  opIndexOpAssign предназначены для многомерного индексирования и нарезки
